﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NLog;

namespace ConsoleService
{
    public class ServerMonitor
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        public static Boolean RunAsService { get; set; }
        public static Boolean CanRun { get; set; }

        private Dictionary<int, Server> _servers;
        private Dictionary<int, Action> _actions;
        private Dictionary<int, Instance> _instances;
        private Dictionary<int, ActionExecuter> _executers;
        private Dictionary<int, ConsoleParser> _consoleparsers;
        private Dictionary<int, LogParser> _logparsers;

        public static void Run()
        {
            var instance = new ServerMonitor();
            System.Threading.Thread.Sleep(3000);
            logger.Info("Server Monitor is running");
            while (CanRun)
            {
                try
                {
                    instance.Process();
                }
                catch (Exception ex)
                {
                    logger.ErrorException("Failed processing", ex);
                }
                System.Threading.Thread.Sleep(3000);
            }
            if (instance._consoleparsers != null && instance._consoleparsers.Count > 0)
            {
                foreach(var parser in instance._consoleparsers)
                {
                    parser.Value.CanRun = false;
                }
            }
            logger.Info("Server Monitor has stopped");
        }

        public ServerMonitor()
        {
            this._actions = new Dictionary<int, Action>();
            this._executers = new Dictionary<int, ActionExecuter>();
            this._instances = new Dictionary<int, Instance>();
            this._servers = new Dictionary<int, Server>();
            this._consoleparsers = new Dictionary<int, ConsoleParser>();
        }

        private void Process()
        {
            if (ConsoleAPI.IsAuthenticated)
            {
                var servers = ConsoleAPI.GetServers();
                if (servers != null && servers.Count() > 0)
                {
                    foreach (var server in servers)
                    {
                        if (this._servers.ContainsKey(server.server_id))
                        {
                            this._servers[server.server_id].Update(server);
                        }
                        else
                        {
                            this._servers.Add(server.server_id, server);
                        }
                    }
                }
                logger.Trace("Loaded servers: {0}", this._servers.Count);

                var activeActions = ConsoleAPI.GetActions();
                logger.Trace("Loaded Actions: {0}", activeActions.Count());
                if (activeActions != null && activeActions.Count() > 0)
                {
                    foreach (var action in activeActions)
                    {
                        if (!this._actions.ContainsKey(action.action_id))
                        {
                            this._actions.Add(action.action_id, action);
                        }
                    }
                }
                logger.Trace("Queued Actions: {0}", this._actions.Count);

                if (this._servers != null && this._servers.Count > 0)
                {
                    foreach (var serverPair in this._servers)
                    {
                        logger.Trace("Start processing server: {0}", serverPair.Key);
                        try
                        {
                            if (!this._instances.ContainsKey(serverPair.Key))
                            {
                                this._instances.Add(serverPair.Key, new Instance(serverPair.Value));
                                logger.Trace("Added instance for server: {0}", serverPair.Key);
                            }
                            var instance = this._instances[serverPair.Key];
                            ActionExecuter executer = null;
                            if (this._executers.ContainsKey(serverPair.Key))
                            {
                                executer = this._executers[serverPair.Key];
                                if (DateTime.Now.Subtract(executer.LastUpdate).TotalMinutes > 5)
                                {
                                    logger.Warn("Executer is idle for more than 5 minutes: {0}", serverPair.Key);
                                    executer.Abort();
                                    this._executers.Remove(serverPair.Key);
                                    executer = null;
                                }
                            }
                            if (this._consoleparsers.ContainsKey(serverPair.Key))
                            {
                                var consoleparser = this._consoleparsers[serverPair.Key];
                                if (DateTime.Now.Subtract(consoleparser.LastUpdate).TotalMinutes > 5)
                                {
                                    logger.Warn("Console parser is idle for more than 5 minutes: {0}", serverPair.Key);
                                    consoleparser.Abort();
                                    consoleparser.PlayerConnected -= PlayerConnectedHandler;
                                    this._consoleparsers.Remove(serverPair.Key);
                                    consoleparser = null;
                                }
                                else
                                {
                                    logger.Trace("Console parser is active: {0} ({1})", serverPair.Key, consoleparser.LastUpdate);
                                }
                            }
                            else
                            {
                                logger.Trace("Creating Console parser: {0}", serverPair.Key);
                                var consoleparser = new ConsoleParser(instance);
                                consoleparser.PlayerConnected += PlayerConnectedHandler;
                                this._consoleparsers.Add(serverPair.Key, consoleparser);
                            }

                            if (executer == null)
                            {
                                var sortedActions = this._actions.Where(p => p.Value.server_id == serverPair.Key).OrderBy(p => p.Value.action_id).Select(p => p.Value);
                                logger.Trace("No executer picking up next action in pool: {0} / {1}",sortedActions.Count(), this._actions.Count);
                                foreach (var action in sortedActions)
                                {
                                    logger.Trace("Checking action: {0} ({1})", action.action_id, action.action);
                                    // We shouldn't have pending actions for a long time... they are either succeeded or failed with a crash 
                                    if (action.is_processing && DateTime.Now.Subtract(action.start_datetime).TotalMinutes > 10)
                                    {
                                        action.is_success = false;
                                        action.is_executed = true;
                                        action.status = "Action Timed Out";
                                        logger.Warn("Action timed out: {0} ({1})", action.action_id, action.action);
                                    }
                                    if (action.is_executed)
                                    {
                                        action.is_processing = false;
                                        action.end_datetime = DateTime.Now;
                                        Action saved;
                                        if (action.action_id >= 0)
                                        {
                                            saved = ConsoleAPI.SaveAction(action);
                                        }
                                        else
                                        {
                                            saved = action;
                                        }
                                        if (saved != null && saved.action_id == action.action_id)
                                        {
                                            this._actions.Remove(action.action_id);
                                            logger.Trace("Action removed: {0} ({1})", action.action_id, this._actions.Count);
                                        }
                                        else
                                        {
                                            logger.Error("Failed to save the Action correctly: {0}",action.action_id);
                                        }
                                        instance.server.last_datetime = DateTime.Now;
                                        logger.Info("Action executed done: {0} ({1})", action.action_id, action.action);
                                    }
                                    if (!action.is_processing && !action.is_executed)
                                    {
                                        logger.Info("Action starting: {0} ({1})", action.action_id, action.action);
                                        action.is_processing = true;
                                        action.is_success = false;
                                        action.status = "Beginning Processing";
                                        action.start_datetime = DateTime.Now;
                                        executer = new ActionExecuter(instance, action);
                                        this._executers.Add(serverPair.Key, executer);
                                        if (!executer.Start())
                                        {
                                            action.is_executed = true;
                                            action.is_processing = false;
                                            action.is_success = false;
                                            action.status = "Action was rejected";
                                            action.end_datetime = DateTime.Now;
                                            logger.Warn("Action is rejected: {0} ({1})", action.action_id, action.action);
                                        }
                                        if (action.action_id >= 0)
                                        {
                                            ConsoleAPI.SaveAction(action);
                                        }
                                        break;
                                    }
                                }
                            }
                            else
                            {
                                if (executer.HasFinished)
                                {
                                    logger.Info("Executer has finished");
                                    executer = null;
                                    this._executers.Remove(serverPair.Key);
                                }
                            }
                            if (executer == null)
                            {
                                // If we didn't start a new action, we just check that the server is still alive or still stopped, depending on what the status should be.
                                var action = instance.CheckStatus();
                                if (action != null)
                                {
                                    // We add the internal action with a negative index in order to allow
                                    var index = 0;
                                    if (this._actions.Count > 0) index = Math.Min(this._actions.Keys.Min(), 0);
                                    action.action_id = index - 1;
                                    logger.Info(string.Format("Adding new Automatic action for server {0}: {1} ({2})", instance.server.server_id, action.action, action.action_id));
                                    this._actions.Add(action.action_id, action);
                                }
                            }
                            ConsoleAPI.SaveServer(instance.server);
                        }
                        catch (Exception ex)
                        {
                            logger.Error(string.Format("Failed during Server processing: {0} : {1}",serverPair.Key, ex.Message));
                        }
                    }
                }


                if (servers == null || servers.Count() <= 0 || (!servers.Any(s => s.is_started || s.is_starting) && this._executers.Count <= 0))
                {
                    logger.Info("No servers are running: Killing Steam Processes");
                    try
                    {
                        var processes = System.Diagnostics.Process.GetProcessesByName(System.Configuration.ConfigurationManager.AppSettings["SteamProcessName"]);
                        if (processes != null && processes.Length > 0)
                        {
                            foreach (var process in processes)
                            {
                                logger.Info("Closing Steam Process: {0}, {1}, {2}", process.Id, process.ProcessName, process.MainModule.FileName);
                                process.CloseMainWindow();
                                System.Threading.Thread.Sleep(1000);
                                process.CloseMainWindow();
                                System.Threading.Thread.Sleep(1000);
                                process.CloseMainWindow();
                                System.Threading.Thread.Sleep(5000);
                                if (process != null && !process.HasExited)
                                {
                                    logger.Info("Killing Steam Process: {0}, {1}, {2}", process.Id, process.ProcessName, process.MainModule.FileName);
                                    process.Kill();
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        logger.Error(string.Format("Failed killing steam process: {0}", ex.Message));
                    }
                }
            } else {
                logger.Error("Server is not authenticated");
                ConsoleAPI.GetSession();
            }
        }

        void PlayerConnectedHandler(Player player)
        {
            ConsoleAPI.PostPlayer(player);
        }
    }
}
